package MontCarlMulti;

import java.io.IOException;

import Abstract.AbstractMC;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;



public class MonteCarloMulti extends AbstractMC{
	public double computeMonteCarlo(final String CallPutFlag, final double S ,final double X,double T ,  
			  double r ,double b,double v,  
			  final int nSteps ,final int nSimulations) 
		      throws InterruptedException, ExecutionException {
		double result= 0.0;
		double dt;
		final double Drift;
		final double vSqrdt;
		
		dt = T/nSteps ;
		Drift= (b - (v*v) / 2 ) * dt;
		vSqrdt = v * Math.sqrt(dt);
		
		    final int numberOfCores = Runtime.getRuntime().availableProcessors();
		    final double blockingCoefficient = 0.1;
		    final int poolSize = (int)(numberOfCores / (1 - blockingCoefficient));
		    
		    System.out.println("Number of Cores available is " + numberOfCores);
		    System.out.println("Pool size is " + poolSize);
		    List<Callable<Double>> partitions = 
		      new ArrayList<Callable<Double>>();
		    for(int k=0; k<(poolSize); k++ ) {
		      partitions.add(new Callable<Double>() {
		        public Double call() throws Exception {
		    		Random rand = new Random();
		    		double Sum = 0;
		    		int  z = 0;
		    		if(CallPutFlag == "c") z = 1;
		    		else if (CallPutFlag == "p") z=-1;
		        	for (int i=0; i<(nSimulations/poolSize); i++){
		    			double St = S;
		    			for (int j=0; j< nSteps; j++){
		    				St = St * Math.exp(Drift + vSqrdt * rand.nextGaussian());
		    			}
		    			Sum = Sum + Math.max(z*(St - X), 0.0);			
		    		}
		          return Sum ;
		        }        
		      });
		    }
		        
		    final ExecutorService executorPool = 
		      Executors.newFixedThreadPool(poolSize);    
		    final List<Future<Double>> valueOfSums = 
		      executorPool.invokeAll(partitions, 10000, TimeUnit.SECONDS);
		      
		    double lastSum = 0.0; 
		    for(final Future<Double> valueOfASum : valueOfSums)  
		      lastSum += valueOfASum.get(); 
		    
		    result = Math.exp(-r * T) * (lastSum/nSimulations);

		    executorPool.shutdown();
		    return result;   
		  } 
	
	public static void main(final String[] args)
		    throws ExecutionException, InterruptedException, IOException { 
		    new MonteCarloMulti().timeAndComputeValue();
		  }
}
